package com.personal.core.xml;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.personal.core.utils.CoreUtil;

/**
 * @author guojuanbo
 */
public class XMLDoc
{
    private Document xmlDoc;

    public static void main(String[] args)
    {
        XMLDoc doc = new XMLDoc();
        try
        {
            doc.load(XMLDoc.class.getResourceAsStream("XZ_alarm_trans_20181217080805.xml"));
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        System.out.println(doc.getRootElement());
    }
    
    /**
     * @param xmlFile xmlFile
     * @throws Exception Exception
     */
    public void loadFromXMLFile(String xmlFile) throws Exception
    {
        try
        {
            FileInputStream stream = new FileInputStream(xmlFile);
            load(stream);
        }
        catch (Exception e)
        {
            throw new Exception("加载XML文件失败");
        }
    }

    /**
     * @param xmlString xmlString
     * @throws Exception Exception
     */
    public void loadFromXMLString(String xmlString) throws Exception
    {
        try
        {
            ByteArrayInputStream stream = new ByteArrayInputStream(xmlString.getBytes("utf-8"));
            load(stream);
        }
        catch (Exception e)
        {
            throw new Exception("加载XML字符串失败");
        }
    }

    /**
     * @param stream stream
     * @throws Exception Exception
     */
    public void load(InputStream stream) throws Exception
    {
        if (stream == null)
        {
            return;
        }
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        DocumentBuilder builder = factory.newDocumentBuilder();

        this.xmlDoc = builder.parse(stream);
        this.xmlDoc.normalize();

        stream.close();
    }

    /**
     * @param rootName rootName
     * @return Element
     * @throws ParserConfigurationException
     */
    public Element initRootElement(String rootName) throws ParserConfigurationException
    {
        if (CoreUtil.isEmpty(rootName))
        {
            return null;
        }

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        DocumentBuilder builder = null;
        try
        {
            builder = factory.newDocumentBuilder();
        }
        catch (ParserConfigurationException e)
        {
            return null;
        }

        this.xmlDoc = builder.newDocument();
        Element e = this.xmlDoc.createElement(rootName);

        this.xmlDoc.appendChild(e);
        return e;
    }

    /**
     * @return Element
     */
    public Element getRootElement()
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return this.xmlDoc.getDocumentElement();
    }

    /**
     * @param elem elem
     * @return List
     */
    public List<Element> getChildList(Element elem)
    {
        List<Element> childs = new ArrayList<Element>();
        if ((this.xmlDoc == null) || (elem == null))
        {
            return null;
        }

        NodeList list = elem.getChildNodes();
        for (int i = 0; i < list.getLength(); ++i)
        {
            if (list.item(i) instanceof Element)
            {
                childs.add((Element) list.item(i));
            }
        }
        return childs;
    }

    /**
     * @param parentNode parentNode
     * @param childName childName
     * @param childValue childValue
     * @return Element
     */
    public Element appendChildNode(Element parentNode, String childName, String childValue)
    {
        if ((this.xmlDoc == null) || (parentNode == null))
        {
            return null;
        }
        if (CoreUtil.isEmpty(childName))
        {
            return null;
        }

        Element child = this.xmlDoc.createElement(childName);
        if (child == null)
        {
            return null;
        }

        child.setTextContent(childValue);

        parentNode.appendChild(child);

        return child;
    }

    /**
     * @param parentNode parentNode
     * @param commentText commentText
     * @return Comment
     */
    public Comment appendComment(Element parentNode, String commentText)
    {
        if ((this.xmlDoc == null) || (parentNode == null))
        {
            return null;
        }
        if (CoreUtil.isEmpty(commentText))
        {
            return null;
        }

        Comment child = this.xmlDoc.createComment(commentText);
        if (child == null)
        {
            return null;
        }

        parentNode.appendChild(child);

        return child;
    }

    /**
     * @param xmlPath xmlPath
     * @return ElementText
     */
    public String getElementText(String xmlPath)
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return getElementTextByPath(this.xmlDoc.getDocumentElement(), xmlPath);
    }

    /**
     * @param xmlPath xmlPath
     * @return Node
     */
    public Node deleteElement(String xmlPath)
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return deleteElementByPath(this.xmlDoc.getDocumentElement(), xmlPath);
    }

    /**
     * @param tagName tagName
     * @return NodeList
     */
    public NodeList getElementsByTagName(String tagName)
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return getElementsByTagName(this.xmlDoc.getDocumentElement(), tagName);
    }

    /**
     * @param bShowVersion bShowVersion
     * @return String
     * @throws IOException IO异常
     */
    public String asXML(boolean bShowVersion) throws IOException
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        StringWriter out = new StringWriter();
        XMLWriter writer = new XMLWriter(out, bShowVersion);
        try
        {
            writer.writeNode(this.xmlDoc.getDocumentElement());
            writer.flush();
        }
        catch (Exception e)
        {
            return "";
        }
        return out.toString();
    }

    /**
     * @param node node
     * @param bShowVersion bShowVersion
     * @return String
     * @throws IOException IOException
     */
    public String asXML(Element node, boolean bShowVersion) throws IOException
    {
        if (node == null)
        {
            return "";
        }

        StringWriter out = new StringWriter();
        XMLWriter writer = new XMLWriter(out, bShowVersion);
        try
        {
            writer.writeNode(node);
            writer.flush();
        }
        catch (Exception e)
        {
            return "";
        }
        return out.toString();
    }

    /**
     * @param tagName tagName
     * @return Element
     */
    public Element createElement(String tagName)
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return this.xmlDoc.createElement(tagName);
    }

    /**
     * @param attrName attrName
     * @return Attr
     */
    public Attr createAttribute(String attrName)
    {
        if (this.xmlDoc == null)
        {
            return null;
        }

        return this.xmlDoc.createAttribute(attrName);
    }

    /**
     * @param express express
     * @param source source
     * @return NodeList
     */
    public static NodeList selectNodes(String express, Object source)
    {
        NodeList result = null;
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        try
        {
            result = (NodeList) xpath.evaluate(express, source, XPathConstants.NODESET);
        }
        catch (XPathExpressionException e)
        {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * @param express express
     * @param source source
     * @return Node
     */
    public static Node selectSingleNode(String express, Object source)
    {
        Node result = null;
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        try
        {
            result = (Node) xpath.evaluate(express, source, XPathConstants.NODE);
        }
        catch (XPathExpressionException e)
        {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * @param node node
     * @param xmlPath xmlPath
     * @return String
     */
    public static String getElementTextByPath(Element node, String xmlPath)
    {
        Element emt = getElementByPath(node, xmlPath);
        if (emt == null)
        {
            return null;
        }

        return emt.getTextContent();
    }

    /**
     * @param node node
     * @param xmlPath xmlPath
     * @return Element
     */
    public static Element getElementByPath(Element node, String xmlPath)
    {
        if ((node == null) || (CoreUtil.isEmpty(xmlPath)))
        {
            return null;
        }

        String[] pathArray = xmlPath.split("/");
        for (String str : pathArray)
        {
            node = getElementChild(node, str);

            if (node == null)
            {
                return null;
            }
        }
        return node;
    }

    private static Element getElementChild(Element parentNode, String childName)
    {
        if ((parentNode == null) || (CoreUtil.isEmpty(childName)))
        {
            return null;
        }

        NodeList ndList = parentNode.getChildNodes();
        if ((ndList == null) || (ndList.getLength() <= 0))
        {
            return null;
        }

        for (int i = 0; i < ndList.getLength(); ++i)
        {
            Node nd = ndList.item(i);
            if ((nd.getNodeName().equals(childName)) && (nd instanceof Element))
            {
                return (Element) nd;
            }
        }

        return null;
    }

    /**
     * @param node node
     * @param xmlPath xmlPath
     * @return Node
     */
    public static Node deleteElementByPath(Element node, String xmlPath)
    {
        Element emt = getElementByPath(node, xmlPath);
        if ((emt == null) || (emt.getParentNode() == null))
        {
            return null;
        }

        return emt.getParentNode().removeChild(emt);
    }

    /**
     * @param node node
     * @param tagName tagName
     * @return NodeList
     */
    public static NodeList getElementsByTagName(Element node, String tagName)
    {
        if ((node == null) || (CoreUtil.isEmpty(tagName)))
        {
            return null;
        }

        return node.getElementsByTagName(tagName);
    }
}